﻿using System;
using System.IO;
using ImageEncryptor.Core.Common.Validators;

namespace ImageEncryptor.Core.Security
{
    #region Decryptor
    public class Decryptor : IDisposable
    {
        #region Private fields & constants

        private const int ENCRYPTED_HEADER_SIZE = 0x90;

        private ValidatorBase _validatorBase;
        private string _carrierBitmapFilePath;
        private string _key;
        byte[] _dataExtractedDecrypted;
        private string _fileName;

        #endregion

        #region Properties

        public string FileNameExtracted
        {
            get { return _fileName; }
        }

        public byte[] DataExtracted
        {
            get { return _dataExtractedDecrypted; }
        }

        #endregion

        #region Contructors

        public Decryptor(string carrierBitmapFilePath, string key)
        {
            _validatorBase = new ValidatorBase();
            _validatorBase.BreakIfAnyArgIsNull<string>(carrierBitmapFilePath, key);
            _carrierBitmapFilePath = carrierBitmapFilePath;
            _key = key;
        }

        #endregion

        #region Public methods

        public void DoJob(bool clearCarrier = false)
        {
            // Read carrier bitmap
            using (var fileStream = new FileStream(_carrierBitmapFilePath, FileMode.Open))
            {
                byte[] headerExtracted = new byte[ENCRYPTED_HEADER_SIZE];
                fileStream.Position = fileStream.Length - ENCRYPTED_HEADER_SIZE;
                fileStream.Read(headerExtracted, 0, ENCRYPTED_HEADER_SIZE);

                // Parse the key
                byte[] key = Convert.FromBase64String(_key);
                if ((key.Length < CommonConstants.MIN_REQUIRED_KEY_SIZE) ||
                    key.Length > CommonConstants.MAX_REQUIRED_KEY_SIZE)
                    throw new FormatException("Invalid key length");

                int keyLength = key.Length;
                int saltOffset = keyLength - AesEncryptor.SALT_SIZE;

                byte[] salt = new byte[AesEncryptor.SALT_SIZE];
                byte[] password = new byte[saltOffset];

                Buffer.BlockCopy(key, 0, password, 0, password.Length);
                Buffer.BlockCopy(key, saltOffset, salt, 0, salt.Length);

                //Decrypt header
                byte[] headerDecrypted =
                    AesEncryptor.Decrypt(headerExtracted, password, salt);

                //Get length of secret data
                int lengthData = 0;
                lengthData |= headerDecrypted[0] << 24;
                lengthData |= headerDecrypted[1] << 16;
                lengthData |= headerDecrypted[2] << 8;
                lengthData |= headerDecrypted[3];

                //Get filename length
                int fileNameLength = headerDecrypted[4];

                // Get file name
                for (int i = 5; i < fileNameLength + 5; i++)
                {
                    _fileName += Convert.ToChar(headerDecrypted[i]);
                }

                // Read data
                int offset = (int)fileStream.Length - lengthData - ENCRYPTED_HEADER_SIZE;

                byte[] dataExtracted = new byte[lengthData];
                fileStream.Position = offset;
                fileStream.Read(dataExtracted, 0, lengthData);

                // Decrypt data
                _dataExtractedDecrypted = AesEncryptor.Decrypt(dataExtracted, password, salt);

                if (clearCarrier)
                {
                    byte[] cleanCarrierData = new byte[offset];
                    fileStream.Position = 0;
                    fileStream.Read(cleanCarrierData, 0, offset);
                    fileStream.Close();
                    using (var fileStreamDestination = new FileStream(_carrierBitmapFilePath, FileMode.Create))
                    {
                        fileStreamDestination.Write(cleanCarrierData, 0, cleanCarrierData.Length);
                    }
                }
            }
        }

        public void SaveExtractedData(string fileName)
        {
            File.WriteAllBytes(fileName, _dataExtractedDecrypted);
        }

        #endregion

        #region Implementations

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _validatorBase = null;
                _carrierBitmapFilePath = null;
                _dataExtractedDecrypted = null;
                _fileName = null;
                _key = null;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

    }
    #endregion
}
